/*
OpenMVL Matrix Vector Library
Copyright (c) 2009 FluidInteractive (R)
 
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
 
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
 
Written by: Nicola Candussi <nicola@fluidinteractive.com>
*/

//layout_element_unary.hpp

#ifndef MVL_LAYOUT_ELEMENT_UNARY_HPP
#define MVL_LAYOUT_ELEMENT_UNARY_HPP

#include "layout.hpp"

namespace mvl {

namespace layout {

template<typename I, typename J, typename Tag, typename Src>
struct element_unary_entry {
    typedef Tag     tag;           //type of entry
    typedef I       i;             //(i,j) location
    typedef J       j;
    typedef Src     src;
    typedef element_unary_entry   type;
};

template<typename LIt, typename TagFn, typename Pos1, typename Size2, typename Order>
struct layout_element_unary1 {
    typedef typename begin2<LIt>::type begin;
    typedef typename end2<LIt>::type end;
    typedef Pos1    pos1;
    typedef Size2   size2;

    template<typename Data, typename It, typename Pos2, typename Condition>
    struct state {
        typedef Data        data;
        typedef It          it;
        typedef Pos2        pos2;
        typedef Condition   condition;
        typedef state  type;
    };

    template<typename NotEnd, typename State> struct update_helper; 

    template<typename State> 
    struct update_helper<meta::bool_<true>, State> 
    {        
        typedef typename meta::deref<typename State::it>::type e;
        typedef typename get2<typename e::i, typename e::j, Order>::type jj;
        typedef typename State::pos2 pos2;
        typedef typename meta::eval_if<
            typename meta::less_equal<jj, pos2>::type,
            meta::next<typename State::it>,
            meta::identity<typename State::it>
        >::type new_it;

        typedef typename meta::next<pos2>::type new_pos2;

        typedef typename get1<pos1, pos2, Order>::type i;
        typedef typename get2<pos1, pos2, Order>::type j;

        typedef typename meta::if_<
            typename meta::equal_to<jj, pos2>::type,
            e,
            entry<i, j, _0_>
        >::type src;

        typedef typename TagFn::template apply<typename src::tag>::type result_tag;

        typedef typename meta::eval_if<
            typename meta::is_same<result_tag, _0_>::type,
            meta::identity<typename State::data>,
            meta::push_back<
                typename State::data,
                element_unary_entry<i, j, result_tag, src>
            >
        >::type new_data;
        typedef state<new_data, new_it, new_pos2, typename meta::not_equal_to<pos2, size2>::type> type;
    };
    
    template<typename State> 
    struct update_helper<meta::bool_<false>, State> 
    {        
        typedef typename State::pos2 pos2;
        typedef typename meta::next<pos2>::type new_pos2;
        typedef typename get1<pos1, pos2, Order>::type i;
        typedef typename get2<pos1, pos2, Order>::type j;

        typedef entry<i, j, _0_> src;

        typedef typename TagFn::template apply<typename src::tag>::type result_tag;

        typedef typename meta::eval_if<
            typename meta::is_same<result_tag, _0_>::type,
            meta::identity<typename State::data>,
            meta::push_back<
                typename State::data,
                element_unary_entry<i, j, result_tag, src>
            >
        >::type new_data;
        typedef state<new_data, typename State::it, new_pos2, typename meta::not_equal_to<pos2, size2>::type> type;
    };

    struct update { 
        template<typename State>
        struct apply : update_helper<
            typename meta::not_equal_to<typename State::it, end>::type,
            State
        > {};
    };

    typedef typename meta::while_<
        state<
            meta::vector<>,
            begin,
            meta::int_<0>,
            typename meta::not_equal_to<meta::int_<0>, size2>::type
        >,
        update
    >::type::data type; 
};

template<typename L, typename TagFn>
struct layout_element_unary {

    typedef typename L::order order;
    typedef typename layout::begin1<L>::type begin;
    typedef typename layout::end1<L>::type end;

    template<typename Data, typename It, typename NumEntries, typename Condition>
    struct state {
        typedef Data        data;
        typedef It          it;
        typedef NumEntries  num_entries;
        typedef Condition   condition;
        typedef state  type;
    };

    struct update {
        template<typename State>
        struct apply {
            typedef typename State::it it;
            typedef typename meta::next<it>::type new_it;
            typedef typename meta::distance<begin, it>::type pos1;
            typedef typename get2<typename L::Rows, typename L::Cols, order>::type size2;
            typedef typename layout_element_unary1<it, TagFn, pos1, size2, order>::type new_row;
            typedef state<
                typename meta::push_back<
                    typename State::data,
                    new_row
                >::type,
                new_it,
                typename meta::plus<
                    typename State::num_entries,
                    typename meta::size<new_row>::type
                >::type,
                typename meta::not_equal_to<new_it, end>::type
            > type;
        };
    };

    typedef typename meta::while_<
        state<
            meta::vector<>,
            begin,
            meta::int_<0>,
            typename meta::not_equal_to<begin, end>::type
        >,
        update
    >::type result;

    typedef sparse_layout<L::Rows::value, L::Cols::value, order, typename result::data, result::num_entries::value> type;
};

} //namespace layout

} //namespace mvl

#endif
